package pl.edu.mimuw.mas.bezier;

import pl.edu.mimuw.mas.chart.Chart;

public class BezierSurface implements ISurface {

	private final ControlPoints cp;

	private final int cpRows;
	private final int cpCols;

	private final int chartRows;
	private final int chartCols;

	private final float[][] bernsteinRowValues;
	private final float[][] bernsteinColValues;

	private float[][] rowSum;

	public BezierSurface(ControlPoints cp, Chart chart) {
		this.cp = cp;

		this.cpRows = (int) cp.getRowsCount();
		this.cpCols = (int) cp.getColumnsCount();

		this.chartRows = (int) chart.getRowsCount();
		this.chartCols = (int) chart.getColumnsCount();

		this.bernsteinRowValues = new float[cpRows][chartRows];
		long binomial = 0L;
		for (int i = 0; i < cpRows; i++) {
			if (i == 0) {
				binomial = 1;
			} else {
				binomial = binomial * (cpRows - i) / i;
			}
			float jVal;
			int j;
			for (jVal = (float) (j = 0); j < chartRows; jVal = (float) ++j / (chartRows - 1)) {
				bernsteinRowValues[i][j] = BernsteinBasisPolynomial.value(
						cpRows - 1, i, binomial, jVal);
			}
		}

		this.bernsteinColValues = new float[cpCols][chartCols];
		binomial = 0L;
		for (int i = 0; i < cpCols; i++) {
			if (i == 0) {
				binomial = 1;
			} else {
				binomial = binomial * (cpCols - i) / i;
			}
			float jVal;
			int j;
			for (jVal = (float) (j = 0); j < chartCols; jVal = (float) ++j / (chartCols - 1)) {
				bernsteinColValues[i][j] = BernsteinBasisPolynomial.value(
						cpCols - 1, i, binomial, jVal);
			}
		}

	}

	public void recalculateCachedData() {
		if (rowSum == null) {
			rowSum = new float[cpCols][chartRows];
		}

		for (int valIndex = 0; valIndex < chartRows; valIndex++) {
			for (int i = 0; i < cpCols; i++) {
				rowSum[i][valIndex] = 0f;
				for (int j = 0; j < cpRows; j++) {
					rowSum[i][valIndex] += cp.getHeight(i, j)
							* bernsteinRowValues[j][valIndex];
				}
			}
		}
	}

	public float value(float x, float y) {
		if (rowSum == null) {
			recalculateCachedData();
		}

		if (x < 0 || x > 1 || y < 0 || y > 1) {
			throw new IllegalArgumentException("Arguments must be within [0,1]");
		}

		int xIndex = Math.round(x * (chartCols - 1));
		int yIndex = Math.round(y * (chartRows - 1));

		float value = 0f;
		for (int i = 0; i < cpCols; i++) {
			value += bernsteinColValues[i][xIndex] * rowSum[i][yIndex];
		}

		return value;
	}

}
